package com.shanzmoo.base.util;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.shanzmoo.base.constant.e.SerializerType;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * @author fs
 */
@UtilityClass
@Slf4j
public class RedisUtil {
    /**
     * redis操作对象
     */
    private static RedisTemplate redisTemplate;
    /**
     * 需要进行初始化的
     */
    private static List<Consumer<RedisTemplate>> needInitList = new Vector<>();
    static{
        SpringUtil.asyncAction(new Consumer<ApplicationContext>() {
            @Override
            public void accept(ApplicationContext applicationContext) {
                init(applicationContext);
            }
        });
    }
    private RedisSerializer<Object> redisSerializer(SerializerType type) {
        if(SerializerType.JDK == type){
            return new JdkSerializationRedisSerializer();
        }else if(SerializerType.CUSTOMIZE == type){
            ObjectMapper objectMapper = new ObjectMapper();
            //反序列化时候遇到不匹配的属性并不抛出异常
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            //序列化时候遇到空对象不抛出异常
            objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
            //反序列化的时候如果是无效子类型,不抛出异常
            objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
            //不使用默认的dateTime进行序列化,
            objectMapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
            //使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类
            objectMapper.registerModule(new JavaTimeModule());
            //启用反序列化所需的类型信息,在属性中添加@class
            objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.EVERYTHING, JsonTypeInfo.As.PROPERTY);
            //配置null值的序列化器
            GenericJackson2JsonRedisSerializer.registerNullValueSerializer(objectMapper, null);
            return new GenericJackson2JsonRedisSerializer(objectMapper);
        }else if(SerializerType.JACKSON == type){
            return new Jackson2JsonRedisSerializer(Object.class);
        }else{
            return new JdkSerializationRedisSerializer();
        }
    }
    public void init(ApplicationContext applicationContext){
        RedisTemplate tempRedisTemplate = null;
        String[] names = applicationContext.getBeanNamesForType(RedisTemplate.class);
        for(String name:names){
            Object temp = applicationContext.getBean(name, RedisTemplate.class);
            if(temp.getClass() == RedisTemplate.class){
                tempRedisTemplate = (RedisTemplate)temp;
                break;
            }
        }
        Assert.notNull(tempRedisTemplate,"redisTemplate不能为空");
        init(tempRedisTemplate);
    }
    public void init(RedisTemplate redisTemplate){
        Assert.isNull(RedisUtil.redisTemplate,"redisTemplate已经初始化");
        RedisSerializer<Object> redisSerializer = redisSerializer(SerializerType.CUSTOMIZE);
        redisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
        redisTemplate.setHashKeySerializer(StringRedisSerializer.UTF_8);
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
        RedisUtil.redisTemplate = redisTemplate;
        if(RedisUtil.needInitList.size()>0){
            Iterator<Consumer<RedisTemplate>> it= RedisUtil.needInitList.iterator();
            while(it.hasNext()){
                action(it.next());
                it.remove();
            }
        }
    }
    public void changeSerializer(SerializerType type){
        RedisUtil.getRedisTemplate(new Consumer<RedisTemplate>() {
            @Override
            public void accept(RedisTemplate redisTemplate) {
                RedisSerializer<Object> redisSerializer = redisSerializer(type);
                redisTemplate.setValueSerializer(redisSerializer);
                redisTemplate.setHashValueSerializer(redisSerializer);
            }
        });
    }

    public <T> T get(String key) {
        try{
            T value = (T)redisTemplate.opsForValue().get(key);
            return value;
        }catch (SerializationException e){
            log.error("反序列化失败:{}",e.getMessage());
            return null;
        }
    }

    public <T> T get(String key, T def) {
        T t = get(key);
        if (t == null) {
            t = def;
        }
        return t;
    }

    public <T> void set(String key, T val, Long timeout,TimeUnit unit) {
        if (timeout == null) {
            redisTemplate.opsForValue().set(key, val);
        } else {
            redisTemplate.opsForValue().set(key, val, timeout, unit);
        }
    }
    public <T> void set(String key, T val, Long minutes) {
        set(key, val, minutes, TimeUnit.MINUTES);
    }

    public <T> void set(String key, T val) {
        set(key, val, null);
    }

    public void del(String key) {
        if (StrUtil.isBlank(key)) {
            return;
        }
        redisTemplate.delete(key);
    }

    public boolean has(String key) {
        if (StrUtil.isBlank(key)) {
            return false;
        }
        return redisTemplate.hasKey(key);
    }

    public Set<String> keys(String pattern) {
        if (StrUtil.isBlank(pattern)) {
            return null;
        }
        return redisTemplate.keys(pattern);
    }


    public void getRedisTemplate(Consumer<RedisTemplate> consumer){
        if(RedisUtil.redisTemplate == null){
            RedisUtil.needInitList.add(consumer);
        }else{
            action(consumer);
        }
    }

    private void action(Consumer<RedisTemplate> consumer){
        try{
            consumer.accept(RedisUtil.redisTemplate);
        }catch (Exception e){
            log.error("getRedisTemplate执行失败:{}",e);
        }
    }

    public static void init() {
    }
}
